/*
    arm instructions ganeration
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

enum arm_cond_code_e {
	COND_EQ = 0x0,
	COND_NE = 0x1,
	COND_CS = 0x2,	/* Unsigned greater or equal */
	COND_CC = 0x3,	/* Unsigned less than */
	COND_MI = 0x4,	/* Negative */
	COND_PL = 0x5,	/* Zero or greater */
	COND_VS = 0x6,	/* Overflow */
	COND_VC = 0x7,	/* No overflow */
	COND_HI = 0x8,	/* Unsigned greater than */
	COND_LS = 0x9,	/* Unsigned less or equal */
	COND_GE = 0xa,
	COND_LT = 0xb,
	COND_GT = 0xc,
	COND_LE = 0xd,
	COND_AL = 0xe,
};

#include "arm-data-instr.h"

static inline void arm_mul(uint8_t rd, uint8_t rm, uint8_t rs)
{
	*gen_code_ptr++ = 0xe0000090 | (rd << 16) | (rs << 8) | rm;
}

static inline void arm_umull(uint8_t rdh, uint8_t rdl, uint8_t rm, uint8_t rs)
{
	*gen_code_ptr++ = 0xe0800090 | (rdh << 16) | (rdl << 12) | (rs << 8) | rm;
}

static inline void arm_smull(uint8_t rdh, uint8_t rdl, uint8_t rm, uint8_t rs)
{
	*gen_code_ptr++ = 0xe0c00090 | (rdh << 16) | (rdl << 12) | (rs << 8) | rm;
}

static inline void arm_ldr_reg_env(int ot, uint8_t reg, uint16_t offset)
{
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe5d40000 | (reg << 12) | (offset & 0xfff);
		break;
	case OT_WORD:
		assert(offset <= 0xff);
		*gen_code_ptr++ = 0xe1d400b0 | (reg << 12) |
			((offset & 0xf0) << 4) | (offset & 0xf);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe5940000 | (reg << 12) | (offset & 0xfff);
		break;
	}
}

static inline void arm_ldr_reg_imm_cond(uint8_t cond, uint8_t reg, uint32_t imm32)
{
	*gen_code_ptr = 0x059f0000 | (cond << 28) | (reg << 12);
	*gen_ldr_str_ptr++ = (uint32_t)gen_code_ptr;
	*gen_ldr_str_ptr++ = imm32;
	gen_code_ptr++;
}

static inline void arm_ldr_reg_imm(uint8_t reg, uint32_t imm32)
{
	*gen_code_ptr = 0xe59f0000 | (reg << 12);
	*gen_ldr_str_ptr++ = (uint32_t)gen_code_ptr;
	*gen_ldr_str_ptr++ = imm32;
	gen_code_ptr++;
}

static inline void arm_ldr_data(int ot, uint8_t reg_addr, uint8_t reg)
{
	assert(reg_addr != reg);
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe5d00000 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_WORD:
		*gen_code_ptr++ = 0xe1d000b0 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe5900000 | (reg << 12) | (reg_addr << 16);
		break;
	}
}

static inline void arm_pop_addr_reg(int ot, uint8_t reg_addr, uint8_t reg)
{
	assert(reg_addr != reg);
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe4d00001 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_WORD:
		*gen_code_ptr++ = 0xe0d000b2 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe4900004 | (reg << 12) | (reg_addr << 16);
		break;
	}
}

static inline void arm_str_env_reg(int ot, uint8_t reg, uint16_t offset)
{
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe5c40000 | (reg << 12) | (offset & 0xfff);
		break;
	case OT_WORD:
		assert(offset <= 0xff);
		*gen_code_ptr++ = 0xe1c400b0 | (reg << 12) |
			((offset & 0xf0) << 4) | (offset & 0xf);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe5840000 | (reg << 12) | (offset & 0xfff);
		break;
	}
}

static inline void arm_str_data(int ot, uint8_t reg_addr, uint8_t reg)
{
	assert(reg_addr != reg);
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe5c00000 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_WORD:
		*gen_code_ptr++ = 0xe1c000b0 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe5800000 | (reg << 12) | (reg_addr << 16);
		break;
	}
}

static inline void arm_push_addr_reg(int ot, uint8_t reg_addr, uint8_t reg)
{
	assert(reg_addr != reg);
	switch (ot) {
	case OT_BYTE:
		*gen_code_ptr++ = 0xe5600001 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_WORD:
		*gen_code_ptr++ = 0xe16000b2 | (reg << 12) | (reg_addr << 16);
		break;
	case OT_LONG:
		*gen_code_ptr++ = 0xe5200004 | (reg << 12) | (reg_addr << 16);
		break;
	}
}

static inline void arm_push_r2(void)
{
	*gen_code_ptr++ = 0xe52d2004;
}

static inline void arm_pop_r2(void)
{
	*gen_code_ptr++ = 0xe49d2004;
}

static inline void arm_push_r3(void)
{
	*gen_code_ptr++ = 0xe52d3004;
}

static inline void arm_pop_r3(void)
{
	*gen_code_ptr++ = 0xe49d3004;
}

static inline void arm_push_lr(void)
{
	*gen_code_ptr++ = 0xe52de004;
}

static inline void arm_pop_lr(void)
{
	*gen_code_ptr++ = 0xe8bd4000;
}

static inline void arm_push_reg_dir_cond(uint8_t cond, int ot, uint8_t reg_addr,
	uint8_t reg, int dir)
{
	assert(reg_addr != reg);
	switch (ot) {
	case OT_BYTE:
		if (dir)
			*gen_code_ptr++ =
				0x04c00001 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		else
			*gen_code_ptr++ =
				0x04400001 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		break;
	case OT_WORD:
		if (dir)
			*gen_code_ptr++ =
				0x00c000b2 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		else
			*gen_code_ptr++ =
				0x004000b2 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		break;
	case OT_LONG:
		if (dir)
			*gen_code_ptr++ =
				0x04800004 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		else
			*gen_code_ptr++ =
				0x04000004 | (cond << 28) | (reg << 12) | (reg_addr << 16);
		break;
	}
}

